Fix Remove(), RemoveAll() to delete symlink properly
[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/paths.h"
24 #include "common/utils/byte_size_literals.h"
25
26 namespace ba = boost::algorithm;
27 namespace bs = boost::system;
28 namespace bf = boost::filesystem;
29
30 namespace {
31
32 unsigned kZipBufSize = 8_kB;
33 unsigned kZipMaxPath = PATH_MAX;
34
35 int64_t GetBlockSizeForPath(const bf::path& path_in_partition) {
36   struct stat stats;
37   if (stat(path_in_partition.string().c_str(), &stats)) {
38     LOG(ERROR) << "stat(" << path_in_partition.string()
39                << ") failed - error code: " << errno;
40     return -1;
41   }
42   return stats.st_blksize;
43 }
44
45 int64_t RoundUpToBlockSizeOf(int64_t size, int64_t block_size) {
46   return ((size + block_size - 1) / block_size) * block_size;
47 }
48
49 class UnzFilePointer {
50  public:
51   UnzFilePointer()
52     : zipFile_(nullptr),
53       fileOpened_(false),
54       currentFileOpened_(false) { }
55
56   ~UnzFilePointer() {
57     if (currentFileOpened_)
58       unzCloseCurrentFile(zipFile_);
59     if (fileOpened_)
60       unzClose(zipFile_);
61   }
62
63   bool Open(const char* zip_path) {
64     zipFile_ = static_cast<unzFile*>(unzOpen(zip_path));
65     if (!zipFile_)
66        return false;
67     fileOpened_ = true;
68     return true;
69   }
70
71   bool OpenCurrent() {
72     if (unzOpenCurrentFile(zipFile_) != UNZ_OK)
73       return false;
74     currentFileOpened_ = true;
75     return true;
76   }
77
78   void CloseCurrent() {
79     if (currentFileOpened_)
80       unzCloseCurrentFile(zipFile_);
81     currentFileOpened_ = false;
82   }
83
84   unzFile* Get() { return zipFile_; }
85
86  private:
87   unzFile* zipFile_;
88   bool fileOpened_;
89   bool currentFileOpened_;
90 };
91
92 }  // namespace
93
94 namespace common_installer {
95
96 FSFlag operator|(FSFlag a, FSFlag b) {
97   return static_cast<FSFlag>(static_cast<int>(a) | static_cast<int>(b));
98 }
99
100 bool SetOwnership(const bf::path& path, uid_t uid, gid_t gid) {
101   int fd = open(path.c_str(), O_RDONLY);
102   if (fd < 0) {
103     LOG(ERROR) << "Can't open directory : " << path;
104     return false;
105   }
106   int ret = fchown(fd, uid, gid);
107   close(fd);
108   if (ret != 0) {
109     LOG(ERROR) << "Failed to change owner of: " << path;
110     return false;
111   }
112   return true;
113 }
114
115 bool SetOwnershipAll(const bf::path& path, uid_t uid, gid_t gid) {
116   if (!SetOwnership(path, uid, gid))
117     return false;
118   if (!bf::is_directory(path))
119     return true;
120   for (bf::recursive_directory_iterator iter(path);
121     iter != bf::recursive_directory_iterator();
122     ++iter) {
123     bf::path current(iter->path());
124     if (bf::is_symlink(symlink_status(current)))
125       continue;
126     if (!SetOwnership(current, uid, gid))
127       return false;
128   }
129   return true;
130 }
131
132 bool CreateDir(const bf::path& path) {
133   if (bf::exists(path))
134     return true;
135
136   boost::system::error_code error;
137   bf::create_directories(path, error);
138
139   if (error) {
140     LOG(ERROR) << "Failed to create directory: "
141                << boost::system::system_error(error).what();
142     return false;
143   }
144   return true;
145 }
146
147 bool SetDirPermissions(const boost::filesystem::path& path,
148                       boost::filesystem::perms permissions) {
149   boost::system::error_code error;
150   bf::permissions(path, permissions, error);
151
152   if (error) {
153     LOG(ERROR) << "Failed to set permissions for directory: " << path
154                << boost::system::system_error(error).what();
155     return false;
156   }
157   return true;
158 }
159
160 bool SetDirOwnershipAndPermissions(const boost::filesystem::path& path,
161                       boost::filesystem::perms permissions, uid_t uid,
162                       gid_t gid) {
163   if (!SetOwnership(path, uid, gid)) {
164     LOG(ERROR) << "Failed to change owner: " << path
165                << "(" << uid << ", " << gid << ")";
166     return false;
167   }
168   if (!SetDirPermissions(path, permissions)) {
169     LOG(ERROR) << "Failed to change permission: " << path
170                << std::oct << permissions;
171     return false;
172   }
173
174   return true;
175 }
176
177 bool CopyOwnershipAndPermissions(const boost::filesystem::path& src,
178                                  const boost::filesystem::path& dst) {
179   if (!bf::exists(src)) {
180     LOG(ERROR) << "Failed to copy ownership and permissions"
181                << " from " << src << " to " << dst;
182     return false;
183   }
184   bf::perms permissions = bf::status(src).permissions();
185   struct stat stats;
186   if (stat(src.c_str(), &stats) != 0)
187     return false;
188   if (!SetDirOwnershipAndPermissions(dst, permissions, stats.st_uid,
189                                     stats.st_gid)) {
190     LOG(ERROR) << "Failed to copy ownership and permissions"
191                << " from " << src << " to " << dst;
192     return false;
193   }
194   return true;
195 }
196
197 bool CopyDir(const bf::path& src, const bf::path& dst,
198              FSFlag flags, bool skip_symlink) {
199   try {
200     // Check whether the function call is valid
201     if (!bf::exists(src) || !bf::is_directory(src)) {
202       LOG(ERROR) << "Source directory " << src
203                  << " does not exist or is not a directory.";
204       return false;
205     }
206     if (!bf::exists(dst)) {
207       // Create the destination directory
208       if (!CreateDir(dst)) {
209         LOG(ERROR) << "Unable to create destination directory" << dst;
210         return false;
211       }
212       if (flags & FS_PRESERVE_OWNERSHIP_AND_PERMISSIONS)
213         CopyOwnershipAndPermissions(src, dst);
214     } else {
215       if (!(flags & (FS_MERGE_SKIP | FS_MERGE_OVERWRITE))) {
216         LOG(ERROR) << "Destination directory " << dst.string()
217                    << " already exists.";
218         return false;
219       }
220       if (flags & (FS_MERGE_OVERWRITE | FS_PRESERVE_OWNERSHIP_AND_PERMISSIONS))
221         CopyOwnershipAndPermissions(src, dst);
222     }
223   } catch (const bf::filesystem_error& error) {
224     LOG(ERROR) << "Failed to copy directory: " << error.what();
225     return false;
226   }
227
228   // Iterate through the source directory
229   for (bf::directory_iterator file(src);
230       file != bf::directory_iterator();
231       ++file) {
232     try {
233       bf::path current(file->path());
234       bf::path target = dst / current.filename();
235
236       if (bf::is_symlink(symlink_status(current))) {
237         if (skip_symlink)
238           continue;
239         if ((flags & (FS_MERGE_SKIP | FS_MERGE_OVERWRITE)) &&
240             bf::exists(target))
241           continue;
242         bs::error_code error;
243         bf::copy_symlink(current, target, error);
244         if (error) {
245           LOG(ERROR) << "Failed to copy symlink: " << current << ", "
246                      << error.message();
247           return false;
248         }
249       } else if (bf::is_directory(current)) {
250         // Found directory: Recursion
251         if (!CopyDir(current, target, flags, skip_symlink)) {
252           return false;
253         }
254       } else {
255         if ((flags & FS_MERGE_SKIP) && bf::exists(target))
256           continue;
257         bf::path destination = target;
258
259         if (flags & FS_COMMIT_COPY_FILE)
260           destination =
261               bf::unique_path(target.parent_path() / "%%%%-%%%%-%%%%-%%%%");
262
263         if (flags & FS_MERGE_OVERWRITE)
264           bf::copy_file(current, destination,
265                         bf::copy_option::overwrite_if_exists);
266         else
267           bf::copy_file(current, destination);
268
269         if (flags & FS_PRESERVE_OWNERSHIP_AND_PERMISSIONS)
270           CopyOwnershipAndPermissions(current, destination);
271
272         if (flags & FS_COMMIT_COPY_FILE) {
273           if (flags & FS_MERGE_OVERWRITE)
274             bf::remove(target);
275           bf::rename(destination, target);
276         }
277       }
278     } catch (const bf::filesystem_error& error) {
279       LOG(ERROR) << "Failed to copy directory: " << error.what();
280       return false;
281     }
282   }
283   return true;
284 }
285
286 bool CopyFile(const bf::path& src, const bf::path& dst) {
287   bs::error_code error;
288
289   bf::copy_file(src, dst, bf::copy_option::overwrite_if_exists, error);
290   if (error) {
291     LOG(WARNING) << "copy file " << src << " due to error [" << error << "]";
292     return false;
293   }
294   return true;
295 }
296
297 bool RestoreBackup(const bf::path& path) {
298   bf::path backup_path = GetBackupPathForPackagePath(path);
299   if (!bf::exists(backup_path) &&
300       !bf::is_symlink(bf::symlink_status(backup_path))) {
301     LOG(WARNING) << backup_path << " does not exist";
302     return true;
303   }
304   return MoveDir(backup_path, path);
305 }
306
307 bool MakeBackup(const bf::path& path) {
308   if (!bf::exists(path) && !bf::is_symlink(bf::symlink_status(path))) {
309     LOG(WARNING) << path << " does not exist";
310     return true;
311   }
312   bf::path backup_path = GetBackupPathForPackagePath(path);
313   return MoveDir(path, backup_path);
314 }
315
316 bool RemoveBackup(const bf::path& path) {
317   bf::path backup_path = GetBackupPathForPackagePath(path);
318   if (!bf::exists(backup_path) &&
319       !bf::is_symlink(bf::symlink_status(backup_path))) {
320     LOG(WARNING) << backup_path << " does not exist";
321     return true;
322   }
323   return RemoveAll(backup_path);
324 }
325
326 bool RemoveAll(const bf::path& path) {
327   if (!exists(path) && !bf::is_symlink(bf::symlink_status(path)))
328     return true;
329
330   bs::error_code error;
331   bf::remove_all(path, error);
332
333   if (error) {
334     LOG(ERROR) << "Cannot remove: " << path << ", " << error.message();
335     return false;
336   }
337
338   return true;
339 }
340
341 bool Remove(const bf::path& path) {
342   if (!exists(path) && !bf::is_symlink(bf::symlink_status(path)))
343     return true;
344
345   bs::error_code error;
346   bf::remove(path, error);
347
348   if (error) {
349     LOG(ERROR) << "Cannot remove: " << path << ", " << error.message();
350     return false;
351   }
352   return true;
353 }
354
355 bool MoveDir(const bf::path& src, const bf::path& dst, FSFlag flags) {
356   if (bf::exists(dst) &&
357       !(flags & (FS_MERGE_SKIP | FS_MERGE_OVERWRITE))) {
358     LOG(ERROR) << "Destination directory does exist: " << dst;
359     return false;
360   }
361
362   bs::error_code error;
363   bf::rename(src, dst, error);
364   if (error) {
365     LOG(WARNING) << "Cannot move directory: " << src << ". Will copy/remove...";
366     if (!CopyDir(src, dst, flags, false)) {
367       LOG(ERROR) << "Cannot copy directory: " << src;
368       return false;
369     }
370     bf::remove_all(src, error);
371     if (error) {
372       LOG(ERROR) << "Cannot remove old directory when coping: " << src;
373       return false;
374     }
375   }
376   return true;
377 }
378
379 bool MoveFile(const bf::path& src, const bf::path& dst, bool force) {
380   if (!force && bf::exists(dst))
381     return false;
382   bs::error_code error;
383   bf::rename(src, dst, error);
384   if (error) {
385     LOG(WARNING) << "Cannot move file: " << src <<
386         ". Will copy/remove... with error [" << error << "]";
387     bf::copy_file(src, dst, bf::copy_option::overwrite_if_exists, error);
388     if (error) {
389       LOG(WARNING) << "Cannot copy file " << src <<
390           " due to error [" << error << "]";
391       return false;
392     }
393     bf::remove_all(src, error);
394     if (error) {
395       LOG(ERROR) << "Cannot remove old file when coping: " << src <<
396           "with error [" << error << "]";
397     }
398   }
399   return true;
400 }
401
402 bool BackupDir(const boost::filesystem::path& src,
403     const boost::filesystem::path& dst, const std::string& entry) {
404   if (!bf::exists(src / entry))
405     return true;
406   if (!MoveDir(src / entry, dst / entry,
407       FS_MERGE_OVERWRITE |
408       FS_COMMIT_COPY_FILE |
409       FS_PRESERVE_OWNERSHIP_AND_PERMISSIONS)) {
410     LOG(ERROR) << "Failed to backup file";
411     return false;
412   }
413
414   return true;
415 }
416
417 int64_t GetUnpackedPackageSize(const bf::path& path) {
418   int64_t size = 0;
419   int64_t block_size = GetBlockSizeForPath(path);
420
421   // if failed to stat path
422   if (block_size == -1)
423     return -1;
424
425   unz_global_info info;
426   unz_file_info64 raw_file_info;
427   char raw_file_name_in_zip[kZipMaxPath];
428
429   unzFile* zip_file = static_cast<unzFile*>(unzOpen(path.string().c_str()));
430   if (zip_file == nullptr) {
431     LOG(ERROR) << "Failed to open the source dir: " << path.string();
432     return -1;
433   }
434
435   if (unzGetGlobalInfo(zip_file, &info) != UNZ_OK) {
436     LOG(ERROR) << "Failed to read global info";
437     unzClose(zip_file);
438     return -1;
439   }
440
441   for (uLong i = 0; i < info.number_entry; i++) {
442     if (unzGetCurrentFileInfo64(zip_file, &raw_file_info, raw_file_name_in_zip,
443         sizeof(raw_file_name_in_zip), nullptr, 0, nullptr, 0) != UNZ_OK) {
444       LOG(ERROR) << "Failed to read file info";
445       unzClose(zip_file);
446       return -1;
447     }
448     size += RoundUpToBlockSizeOf(raw_file_info.uncompressed_size, block_size);
449     unzGoToNextFile(zip_file);
450   }
451
452   // FIXME: calculate space needed for directories
453   unzClose(zip_file);
454   return size;
455 }
456
457 int64_t GetDirectorySize(const boost::filesystem::path& path) {
458   int64_t block_size = GetBlockSizeForPath(path);
459
460   // if failed to stat path
461   if (block_size == -1)
462     return -1;
463
464   int64_t size = 0;
465   for (bf::recursive_directory_iterator iter(path);
466       iter != bf::recursive_directory_iterator(); ++iter) {
467       struct stat buf;
468       if (lstat(iter->path().c_str(), &buf) == -1) {
469         LOG(ERROR) << "lstat() failed for: " << iter->path();
470         return -1;
471       }
472       size += RoundUpToBlockSizeOf(buf.st_size, block_size);
473   }
474
475   // FIXME: block size for external device may differ...
476   return size;
477 }
478
479 bool CheckFreeSpaceAtPath(int64_t required_size,
480     const boost::filesystem::path& target_location) {
481   bs::error_code error;
482   boost::filesystem::path root = target_location;
483   while (!bf::exists(root) && root != root.root_path())
484     root = root.parent_path();
485
486   if (!bf::exists(root)) {
487     LOG(ERROR) << "No mount point for path: " << target_location;
488     return false;
489   }
490   bf::space_info space_info = bf::space(root, error);
491   if (error) {
492     LOG(ERROR) << "Failed to get space_info: " << error.message();
493     return false;
494   }
495
496   return (space_info.free >= static_cast<uint64_t>(required_size));
497 }
498
499 boost::filesystem::path GenerateTmpDir(const bf::path &app_path) {
500   boost::filesystem::path install_tmp_dir;
501   boost::filesystem::path tmp_dir(app_path);
502
503   do {
504     boost::filesystem::path model;
505     boost::filesystem::path unique_dir =
506         boost::filesystem::unique_path(model = "unpack-%%%%%%");
507
508     install_tmp_dir = tmp_dir /= unique_dir;
509   } while (boost::filesystem::exists(install_tmp_dir) &&
510            boost::filesystem::is_directory(install_tmp_dir));
511
512   return install_tmp_dir;
513 }
514
515 boost::filesystem::path GenerateTemporaryPath(
516     const boost::filesystem::path& path) {
517   bf::path pattern = path;
518   pattern += "-%%%%%%";
519   bf::path tmp_path;
520   do {
521     tmp_path = boost::filesystem::unique_path(pattern);
522   } while (boost::filesystem::exists(tmp_path));
523   return tmp_path;
524 }
525
526 bool ExtractToTmpDir(const char* zip_path,
527                      const boost::filesystem::path& tmp_dir) {
528   return ExtractToTmpDir(zip_path, tmp_dir, "");
529 }
530
531 bool ExtractToTmpDir(const char* zip_path, const bf::path& tmp_dir,
532                      const std::string& filter_prefix) {
533   unz_global_info info;
534   char read_buffer[kZipBufSize];
535   unz_file_info raw_file_info;
536   char raw_file_name_in_zip[kZipMaxPath];
537
538   current_path(tmp_dir);
539
540   UnzFilePointer zip_file;
541   if (!zip_file.Open(zip_path)) {
542     LOG(WARNING) << "Failed to open the source dir: " << zip_path;
543     return false;
544   }
545
546   if (unzGetGlobalInfo(zip_file.Get(), &info) != UNZ_OK) {
547     LOG(ERROR) << "Failed to read global info";
548     return false;
549   }
550
551   for (uLong i = 0; i < info.number_entry; i++) {
552     if (unzGetCurrentFileInfo(zip_file.Get(),
553                               &raw_file_info,
554                               raw_file_name_in_zip,
555                               sizeof(raw_file_name_in_zip),
556                               nullptr,
557                               0,
558                               nullptr,
559                               0) != UNZ_OK) {
560       LOG(ERROR) << "Failed to read file info";
561       return false;
562     }
563
564     if (raw_file_name_in_zip[0] == '\0')
565       return false;
566
567     // unpack if filter is empty or path is matched
568     if (filter_prefix.empty() ||
569         std::string(raw_file_name_in_zip).find(filter_prefix) == 0) {
570       bf::path filename_in_zip_path(raw_file_name_in_zip);
571
572       // prevent "directory climbing" attack
573       if (HasDirectoryClimbing(filename_in_zip_path)) {
574         LOG(ERROR) << "Relative path in widget in malformed";
575         return false;
576       }
577
578       if (!filename_in_zip_path.parent_path().empty()) {
579         if (!CreateDir(filename_in_zip_path.parent_path())) {
580           LOG(ERROR) << "Failed to create directory: "
581               << filename_in_zip_path.parent_path();
582           return false;
583         }
584       }
585
586       if (!zip_file.OpenCurrent()) {
587         LOG(ERROR) << "Failed to open file";
588         return false;
589       }
590
591       if (!is_directory(filename_in_zip_path)) {
592         FILE* out = fopen(raw_file_name_in_zip, "wbx");
593         if (!out) {
594           LOG(ERROR) << "Failed to open destination ";
595           return false;
596         }
597
598         int ret = UNZ_OK;
599         do {
600           ret = unzReadCurrentFile(zip_file.Get(), read_buffer, kZipBufSize);
601           if (ret < 0) {
602             LOG(ERROR) << "Failed to read data: " << ret;
603             fclose(out);
604             return false;
605           } else {
606             fwrite(read_buffer, sizeof(char), ret, out);
607           }
608         } while (ret > 0);
609
610         fclose(out);
611       }
612
613       zip_file.CloseCurrent();
614     }
615
616     if ((i+1) < info.number_entry) {
617       if (unzGoToNextFile(zip_file.Get()) != UNZ_OK) {
618         LOG(ERROR) << "Failed to read next file";
619         return false;
620       }
621     }
622   }
623
624   return true;
625 }
626
627 bool CheckPathInZipArchive(const char* zip_archive_path,
628                            const boost::filesystem::path& relative_zip_path,
629                            bool* found) {
630   *found = false;
631   UnzFilePointer zip_file;
632   if (!zip_file.Open(zip_archive_path)) {
633     LOG(ERROR) << "Failed to open the source dir: " << zip_archive_path;
634     return false;
635   }
636
637   unz_global_info info;
638   if (unzGetGlobalInfo(zip_file.Get(), &info) != UNZ_OK) {
639     LOG(ERROR) << "Failed to read global info";
640     return false;
641   }
642
643   char raw_file_name_in_zip[kZipMaxPath];
644   unz_file_info raw_file_info;
645   for (uLong i = 0; i < info.number_entry; i++) {
646     if (unzGetCurrentFileInfo(zip_file.Get(),
647                               &raw_file_info,
648                               raw_file_name_in_zip,
649                               sizeof(raw_file_name_in_zip),
650                               nullptr,
651                               0,
652                               nullptr,
653                               0) != UNZ_OK) {
654       LOG(ERROR) << "Failed to read file info";
655       return false;
656     }
657
658     if (raw_file_name_in_zip[0] == '\0')
659       return false;
660
661     if (relative_zip_path.string() == raw_file_name_in_zip) {
662       *found = true;
663       return true;
664     }
665
666     if ((i + 1) < info.number_entry) {
667       if (unzGoToNextFile(zip_file.Get()) != UNZ_OK) {
668         LOG(ERROR) << "Failed to read next file";
669         return false;
670       }
671     }
672   }
673
674   return true;
675 }
676
677 bool HasDirectoryClimbing(const boost::filesystem::path& path) {
678   std::vector<std::string> segments;
679   ba::split(segments, path.string(), ba::is_any_of("/\\"));
680   return std::any_of(segments.begin(), segments.end(),
681                   [](const std::string& segment) {
682                     return segment == "..";
683                   });
684 }
685
686 boost::filesystem::path MakeRelativePath(const boost::filesystem::path& input,
687                                          const boost::filesystem::path& base) {
688   if (input.string().find(base.string()) == std::string::npos) {
689       LOG(ERROR) << base.string() << " is not base path for " << input.string();
690       return input;
691   }
692   return input.string().substr(base.string().length() + 1);
693 }
694
695 bool IsSubDir(const boost::filesystem::path& path,
696     const boost::filesystem::path& root) {
697   boost::filesystem::path p = path;
698   while (p != boost::filesystem::path()) {
699     if (bf::equivalent(p, root))
700       return true;
701     else
702       p = p.parent_path();
703   }
704   return false;
705 }
706
707 bf::path RelativePath(const bf::path& from,
708                                      const bf::path& to) {
709   bf::path::const_iterator itr_path = from.begin();
710   bf::path::const_iterator itr_relative_to = to.begin();
711   while (itr_path != from.end() && itr_relative_to != to.end() &&
712          *itr_path == *itr_relative_to) {
713     ++itr_path;
714     ++itr_relative_to;
715   }
716
717   bf::path result;
718   if (itr_relative_to != to.end()) {
719     ++itr_relative_to;
720     while (itr_relative_to != to.end()) {
721       result /= "..";
722       ++itr_relative_to;
723     }
724   }
725
726   while (itr_path != from.end()) {
727     result /= *itr_path;
728     ++itr_path;
729   }
730
731   bs::error_code error;
732   bf::path resolved_path = bf::canonical(result, error);
733   if (error) {
734     LOG(ERROR) << "Failed to get canonical path";
735     return {};
736   }
737
738   if (from != resolved_path) {
739     LOG(ERROR) << "Failed to get right relative path :" << resolved_path;
740     return {};
741   }
742
743   return result;
744 }
745
746 }  // namespace common_installer