1 /* 2014, Copyright © Intel Coporation, license APACHE-2.0, see LICENSE file */
3 #include "common/utils/file_util.h"
7 #include <linux/limits.h>
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>
17 #include <manifest_parser/utils/logging.h>
23 #include "common/utils/byte_size_literals.h"
25 namespace ba = boost::algorithm;
26 namespace bs = boost::system;
27 namespace bf = boost::filesystem;
31 unsigned kZipBufSize = 8_kB;
32 unsigned kZipMaxPath = PATH_MAX;
34 int64_t GetBlockSizeForPath(const bf::path& path_in_partition) {
36 if (stat(path_in_partition.string().c_str(), &stats)) {
37 LOG(ERROR) << "stat(" << path_in_partition.string()
38 << ") failed - error code: " << errno;
41 return stats.st_blksize;
44 int64_t RoundUpToBlockSizeOf(int64_t size, int64_t block_size) {
45 return ((size + block_size - 1) / block_size) * block_size;
48 class UnzFilePointer {
53 currentFileOpened_(false) { }
56 if (currentFileOpened_)
57 unzCloseCurrentFile(zipFile_);
62 bool Open(const char* zip_path) {
63 zipFile_ = static_cast<unzFile*>(unzOpen(zip_path));
71 if (unzOpenCurrentFile(zipFile_) != UNZ_OK)
73 currentFileOpened_ = true;
78 if (currentFileOpened_)
79 unzCloseCurrentFile(zipFile_);
80 currentFileOpened_ = false;
83 unzFile* Get() { return zipFile_; }
88 bool currentFileOpened_;
93 namespace common_installer {
95 FSFlag operator|(FSFlag a, FSFlag b) {
96 return static_cast<FSFlag>(static_cast<int>(a) | static_cast<int>(b));
99 bool SetOwnership(const bf::path& path, uid_t uid, gid_t gid) {
100 int fd = open(path.c_str(), O_RDONLY);
102 LOG(ERROR) << "Can't open directory : " << path;
105 int ret = fchown(fd, uid, gid);
108 LOG(ERROR) << "Failed to change owner of: " << path;
114 bool SetOwnershipAll(const bf::path& path, uid_t uid, gid_t gid) {
115 if (!SetOwnership(path, uid, gid))
117 if (!bf::is_directory(path))
119 for (bf::recursive_directory_iterator iter(path);
120 iter != bf::recursive_directory_iterator();
122 bf::path current(iter->path());
123 if (bf::is_symlink(symlink_status(current)))
125 if (!SetOwnership(current, uid, gid))
131 bool CreateDir(const bf::path& path) {
132 if (bf::exists(path))
135 boost::system::error_code error;
136 bf::create_directories(path, error);
139 LOG(ERROR) << "Failed to create directory: "
140 << boost::system::system_error(error).what();
146 bool SetDirPermissions(const boost::filesystem::path& path,
147 boost::filesystem::perms permissions) {
148 boost::system::error_code error;
149 bf::permissions(path, permissions, error);
152 LOG(ERROR) << "Failed to set permissions for directory: " << path
153 << boost::system::system_error(error).what();
159 bool SetDirOwnershipAndPermissions(const boost::filesystem::path& path,
160 boost::filesystem::perms permissions, uid_t uid,
162 if (!SetOwnership(path, uid, gid)) {
163 LOG(ERROR) << "Failed to change owner: " << path
164 << "(" << uid << ", " << gid << ")";
167 if (!SetDirPermissions(path, permissions)) {
168 LOG(ERROR) << "Failed to change permission: " << path
169 << std::oct << permissions;
176 bool CopyOwnershipAndPermissions(const boost::filesystem::path& src,
177 const boost::filesystem::path& dst) {
178 if (!bf::exists(src)) {
179 LOG(ERROR) << "Failed to copy ownership and permissions"
180 << " from " << src << " to " << dst;
183 bf::perms permissions = bf::status(src).permissions();
185 stat(src.c_str(), &stats);
186 if (!SetDirOwnershipAndPermissions(dst, permissions, stats.st_uid,
188 LOG(ERROR) << "Failed to copy ownership and permissions"
189 << " from " << src << " to " << dst;
195 bool CopyDir(const bf::path& src, const bf::path& dst,
196 FSFlag flags, bool skip_symlink) {
198 // Check whether the function call is valid
199 if (!bf::exists(src) || !bf::is_directory(src)) {
200 LOG(ERROR) << "Source directory " << src
201 << " does not exist or is not a directory.";
204 if (!bf::exists(dst)) {
205 // Create the destination directory
206 if (!CreateDir(dst)) {
207 LOG(ERROR) << "Unable to create destination directory" << dst;
210 if (flags & FS_PRESERVE_OWNERSHIP_AND_PERMISSIONS)
211 CopyOwnershipAndPermissions(src, dst);
213 if (!(flags & (FS_MERGE_SKIP | FS_MERGE_OVERWRITE))) {
214 LOG(ERROR) << "Destination directory " << dst.string()
215 << " already exists.";
218 if (flags & (FS_MERGE_OVERWRITE | FS_PRESERVE_OWNERSHIP_AND_PERMISSIONS))
219 CopyOwnershipAndPermissions(src, dst);
221 } catch (const bf::filesystem_error& error) {
222 LOG(ERROR) << "Failed to copy directory: " << error.what();
226 // Iterate through the source directory
227 for (bf::directory_iterator file(src);
228 file != bf::directory_iterator();
231 bf::path current(file->path());
232 bf::path target = dst / current.filename();
234 if (bf::is_symlink(symlink_status(current))) {
237 if ((flags & (FS_MERGE_SKIP | FS_MERGE_OVERWRITE)) &&
240 bs::error_code error;
241 bf::copy_symlink(current, target, error);
243 LOG(ERROR) << "Failed to copy symlink: " << current << ", "
247 } else if (bf::is_directory(current)) {
248 // Found directory: Recursion
249 if (!CopyDir(current, target, flags, skip_symlink)) {
253 if ((flags & FS_MERGE_SKIP) && bf::exists(target))
255 bf::path destination = target;
257 if (flags & FS_COMMIT_COPY_FILE)
259 bf::unique_path(target.parent_path() / "%%%%-%%%%-%%%%-%%%%");
261 if (flags & FS_MERGE_OVERWRITE)
262 bf::copy_file(current, destination,
263 bf::copy_option::overwrite_if_exists);
265 bf::copy_file(current, destination);
267 if (flags & FS_PRESERVE_OWNERSHIP_AND_PERMISSIONS)
268 CopyOwnershipAndPermissions(current, destination);
270 if (flags & FS_COMMIT_COPY_FILE) {
271 if (flags & FS_MERGE_OVERWRITE)
273 bf::rename(destination, target);
276 } catch (const bf::filesystem_error& error) {
277 LOG(ERROR) << "Failed to copy directory: " << error.what();
284 bool CopyFile(const bf::path& src, const bf::path& dst) {
285 bs::error_code error;
287 bf::copy_file(src, dst, bf::copy_option::overwrite_if_exists, error);
289 LOG(WARNING) << "copy file " << src << " due to error [" << error << "]";
295 bool RemoveAll(const bf::path& path) {
299 bs::error_code error;
300 bf::remove_all(path, error);
303 LOG(ERROR) << "Cannot remove: " << path << ", " << error.message();
310 bool Remove(const bf::path& path) {
314 bs::error_code error;
315 bf::remove(path, error);
318 LOG(ERROR) << "Cannot remove: " << path << ", " << error.message();
324 bool MoveDir(const bf::path& src, const bf::path& dst, FSFlag flags) {
325 if (bf::exists(dst) &&
326 !(flags & (FS_MERGE_SKIP | FS_MERGE_OVERWRITE))) {
327 LOG(ERROR) << "Destination directory does exist: " << dst;
331 bs::error_code error;
332 bf::rename(src, dst, error);
334 LOG(WARNING) << "Cannot move directory: " << src << ". Will copy/remove...";
335 if (!CopyDir(src, dst, flags, false)) {
336 LOG(ERROR) << "Cannot copy directory: " << src;
339 bf::remove_all(src, error);
341 LOG(ERROR) << "Cannot remove old directory when coping: " << src;
348 bool MoveFile(const bf::path& src, const bf::path& dst, bool force) {
349 if (!force && bf::exists(dst))
351 bs::error_code error;
352 bf::rename(src, dst, error);
354 LOG(WARNING) << "Cannot move file: " << src <<
355 ". Will copy/remove... with error [" << error << "]";
356 bf::copy_file(src, dst, bf::copy_option::overwrite_if_exists, error);
358 LOG(WARNING) << "Cannot copy file " << src <<
359 " due to error [" << error << "]";
362 bf::remove_all(src, error);
364 LOG(ERROR) << "Cannot remove old file when coping: " << src <<
365 "with error [" << error << "]";
371 int64_t GetUnpackedPackageSize(const bf::path& path) {
373 int64_t block_size = GetBlockSizeForPath(path);
375 // if failed to stat path
376 if (block_size == -1)
379 unz_global_info info;
380 unz_file_info64 raw_file_info;
381 char raw_file_name_in_zip[kZipMaxPath];
383 unzFile* zip_file = static_cast<unzFile*>(unzOpen(path.string().c_str()));
384 if (zip_file == nullptr) {
385 LOG(ERROR) << "Failed to open the source dir: " << path.string();
389 if (unzGetGlobalInfo(zip_file, &info) != UNZ_OK) {
390 LOG(ERROR) << "Failed to read global info";
395 for (uLong i = 0; i < info.number_entry; i++) {
396 if (unzGetCurrentFileInfo64(zip_file, &raw_file_info, raw_file_name_in_zip,
397 sizeof(raw_file_name_in_zip), nullptr, 0, nullptr, 0) != UNZ_OK) {
398 LOG(ERROR) << "Failed to read file info";
402 size += RoundUpToBlockSizeOf(raw_file_info.uncompressed_size, block_size);
403 unzGoToNextFile(zip_file);
406 // FIXME: calculate space needed for directories
411 int64_t GetDirectorySize(const boost::filesystem::path& path) {
412 int64_t block_size = GetBlockSizeForPath(path);
414 // if failed to stat path
415 if (block_size == -1)
419 for (bf::recursive_directory_iterator iter(path);
420 iter != bf::recursive_directory_iterator(); ++iter) {
422 if (lstat(iter->path().c_str(), &buf) == -1) {
423 LOG(ERROR) << "lstat() failed for: " << iter->path();
426 size += RoundUpToBlockSizeOf(buf.st_size, block_size);
429 // FIXME: block size for external device may differ...
433 boost::filesystem::path GenerateTmpDir(const bf::path &app_path) {
434 boost::filesystem::path install_tmp_dir;
435 boost::filesystem::path tmp_dir(app_path);
438 boost::filesystem::path model;
439 boost::filesystem::path unique_dir =
440 boost::filesystem::unique_path(model = "unpack-%%%%%%");
442 install_tmp_dir = tmp_dir /= unique_dir;
443 } while (boost::filesystem::exists(install_tmp_dir) &&
444 boost::filesystem::is_directory(install_tmp_dir));
446 return install_tmp_dir;
449 boost::filesystem::path GenerateTemporaryPath(
450 const boost::filesystem::path& path) {
451 bf::path pattern = path;
452 pattern += "-%%%%%%";
455 tmp_path = boost::filesystem::unique_path(pattern);
456 } while (boost::filesystem::exists(tmp_path));
460 bool ExtractToTmpDir(const char* zip_path,
461 const boost::filesystem::path& tmp_dir) {
462 return ExtractToTmpDir(zip_path, tmp_dir, "");
465 bool ExtractToTmpDir(const char* zip_path, const bf::path& tmp_dir,
466 const std::string& filter_prefix) {
467 unz_global_info info;
468 char read_buffer[kZipBufSize];
469 unz_file_info raw_file_info;
470 char raw_file_name_in_zip[kZipMaxPath];
472 current_path(tmp_dir);
474 UnzFilePointer zip_file;
475 if (!zip_file.Open(zip_path)) {
476 LOG(WARNING) << "Failed to open the source dir: " << zip_path;
480 if (unzGetGlobalInfo(zip_file.Get(), &info) != UNZ_OK) {
481 LOG(ERROR) << "Failed to read global info";
485 for (uLong i = 0; i < info.number_entry; i++) {
486 if (unzGetCurrentFileInfo(zip_file.Get(),
488 raw_file_name_in_zip,
489 sizeof(raw_file_name_in_zip),
494 LOG(ERROR) << "Failed to read file info";
498 if (raw_file_name_in_zip[0] == '\0')
501 // unpack if filter is empty or path is matched
502 if (filter_prefix.empty() ||
503 std::string(raw_file_name_in_zip).find(filter_prefix) == 0) {
504 bf::path filename_in_zip_path(raw_file_name_in_zip);
506 // prevent "directory climbing" attack
507 if (HasDirectoryClimbing(filename_in_zip_path)) {
508 LOG(ERROR) << "Relative path in widget in malformed";
512 if (!filename_in_zip_path.parent_path().empty()) {
513 if (!CreateDir(filename_in_zip_path.parent_path())) {
514 LOG(ERROR) << "Failed to create directory: "
515 << filename_in_zip_path.parent_path();
520 if (!zip_file.OpenCurrent()) {
521 LOG(ERROR) << "Failed to open file";
525 if (!is_directory(filename_in_zip_path)) {
526 FILE* out = fopen(raw_file_name_in_zip, "wbx");
528 LOG(ERROR) << "Failed to open destination ";
534 ret = unzReadCurrentFile(zip_file.Get(), read_buffer, kZipBufSize);
536 LOG(ERROR) << "Failed to read data: " << ret;
540 fwrite(read_buffer, sizeof(char), ret, out);
547 zip_file.CloseCurrent();
550 if ((i+1) < info.number_entry) {
551 if (unzGoToNextFile(zip_file.Get()) != UNZ_OK) {
552 LOG(ERROR) << "Failed to read next file";
561 bool CheckPathInZipArchive(const char* zip_archive_path,
562 const boost::filesystem::path& relative_zip_path,
565 UnzFilePointer zip_file;
566 if (!zip_file.Open(zip_archive_path)) {
567 LOG(ERROR) << "Failed to open the source dir: " << zip_archive_path;
571 unz_global_info info;
572 if (unzGetGlobalInfo(zip_file.Get(), &info) != UNZ_OK) {
573 LOG(ERROR) << "Failed to read global info";
577 char raw_file_name_in_zip[kZipMaxPath];
578 unz_file_info raw_file_info;
579 for (uLong i = 0; i < info.number_entry; i++) {
580 if (unzGetCurrentFileInfo(zip_file.Get(),
582 raw_file_name_in_zip,
583 sizeof(raw_file_name_in_zip),
588 LOG(ERROR) << "Failed to read file info";
592 if (raw_file_name_in_zip[0] == '\0')
595 if (relative_zip_path.string() == raw_file_name_in_zip) {
600 if ((i + 1) < info.number_entry) {
601 if (unzGoToNextFile(zip_file.Get()) != UNZ_OK) {
602 LOG(ERROR) << "Failed to read next file";
611 bool HasDirectoryClimbing(const boost::filesystem::path& path) {
612 std::vector<std::string> segments;
613 ba::split(segments, path.string(), ba::is_any_of("/\\"));
614 return std::any_of(segments.begin(), segments.end(),
615 [](const std::string& segment) {
616 return segment == "..";
620 boost::filesystem::path MakeRelativePath(const boost::filesystem::path& input,
621 const boost::filesystem::path& base) {
622 if (input.string().find(base.string()) == std::string::npos) {
623 LOG(ERROR) << base.string() << " is not base path for " << input.string();
626 return input.string().substr(base.string().length() + 1);
629 bool IsSubDir(const boost::filesystem::path& path,
630 const boost::filesystem::path& root) {
631 boost::filesystem::path p = path;
632 while (p != boost::filesystem::path()) {
633 if (bf::equivalent(p, root))
641 } // namespace common_installer