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