2 * Copyright (c) 2015 Samsung Electronics Co., Ltd All Rights Reserved
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 #include "un_zip_extract_request.h"
28 #include "common/logger.h"
29 #include "common/tools.h"
31 #include "archive_file.h"
32 #include "archive_utils.h"
33 #include "filesystem_file.h"
39 using namespace common;
40 using common::tools::GetErrorString;
42 FilePathStatus getPathStatus(const std::string& path) {
48 std::string npath = removeTrailingDirectorySlashFromPath(path);
51 if (stat(npath.c_str(), &sb) == -1) {
54 if (sb.st_mode & S_IFDIR) {
61 void divideToPathAndName(const std::string& filepath, std::string& out_path,
62 std::string& out_name) {
64 size_t pos_last_dir = filepath.find_last_of("/\\");
65 if (pos_last_dir == std::string::npos) {
69 out_path = filepath.substr(0, pos_last_dir + 1);
70 out_name = filepath.substr(pos_last_dir + 1);
74 void createMissingDirectories(const std::string& path, bool check_first = true) {
77 const FilePathStatus path_status = getPathStatus(path);
78 // LoggerD("[%s] status: %d", path.c_str(), path_status);
79 if (FPS_DIRECTORY == path_status) {
84 const size_t extract_path_len = path.length();
85 for (size_t i = 0; i < extract_path_len; ++i) {
86 const char& cur = path[i];
87 if ((('\\' == cur || '/' == cur) && i > 0) || // handle left side from current /
88 (extract_path_len - 1 == i)) { // handle last subdirectory path
89 const std::string left_part = path.substr(0, i + 1);
90 const FilePathStatus status = getPathStatus(left_part);
91 // LoggerD("left_part: [%s] status:%d", left_part.c_str(), status);
93 if (FPS_DIRECTORY != status) {
94 // permissions would be changed after extract process would be finished,
95 // for now using default 0775
96 if (mkdir(left_part.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == -1) {
97 int errno_copy = errno;
98 LoggerE("Couldn't create new directory: %s errno: %d (%s)", left_part.c_str(), errno,
99 GetErrorString(errno_copy).c_str());
106 void changeFileAccessAndModifyDate(const std::string& filepath, tm_unz tmu_date) {
110 newdate.tm_sec = tmu_date.tm_sec;
111 newdate.tm_min = tmu_date.tm_min;
112 newdate.tm_hour = tmu_date.tm_hour;
113 newdate.tm_mday = tmu_date.tm_mday;
114 newdate.tm_mon = tmu_date.tm_mon;
116 if (tmu_date.tm_year > 1900) {
117 newdate.tm_year = tmu_date.tm_year - 1900;
119 newdate.tm_year = tmu_date.tm_year;
121 newdate.tm_isdst = -1;
123 ut.actime = ut.modtime = mktime(&newdate);
124 if (utime(filepath.c_str(), &ut) == -1) {
125 LoggerE("Couldn't set time for: [%s] errno: %s", filepath.c_str(),
126 GetErrorString(errno).c_str());
130 PlatformResult UnZipExtractRequest::execute(UnZip& owner, const std::string& extract_path,
131 const std::string& base_strip_path,
132 BaseProgressCallback* callback) {
134 UnZipExtractRequest req(owner, extract_path, base_strip_path, callback);
135 if (!req.m_callback) {
136 return LogAndCreateResult(ErrorCode::UNKNOWN_ERR, "Problem with callback functionality",
137 ("Callback is null"));
142 UnZipExtractRequest::UnZipExtractRequest(UnZip& owner, const std::string& extract_path,
143 const std::string& base_strip_path,
144 BaseProgressCallback* callback)
148 m_extract_path(extract_path),
149 m_base_strip_path(base_strip_path),
150 m_callback(callback),
154 m_delete_output_file(false),
155 m_close_unz_current_file(false),
157 m_new_dir_status(FPS_NOT_EXIST),
159 m_is_directory_entry(false) {
161 m_filename_inzip[0] = '\0';
164 PlatformResult UnZipExtractRequest::run() {
167 PlatformResult result = getCurrentFileInfo();
168 if (result.error_code() != ErrorCode::NO_ERROR) {
169 LoggerE("Error: %s", result.message().c_str());
173 if (m_is_directory_entry) {
174 result = handleDirectoryEntry();
176 result = handleFileEntry();
182 UnZipExtractRequest::~UnZipExtractRequest() {
186 fclose(m_output_file);
187 m_output_file = NULL;
190 if (m_delete_output_file && !m_is_directory_entry) {
191 if (std::remove(m_output_filepath.c_str()) != 0) {
193 "Couldn't remove partial file! "
194 "std::remove(\"%s\") failed with errno: %s",
195 m_output_filepath.c_str(), GetErrorString(errno).c_str());
202 if (m_close_unz_current_file) {
203 int err = unzCloseCurrentFile(m_owner.m_unzip);
205 LoggerW("%s", getArchiveLogMessage(err, "unzCloseCurrentFile()").c_str());
210 PlatformResult UnZipExtractRequest::getCurrentFileInfo() {
212 int err = unzGetCurrentFileInfo(m_owner.m_unzip, &m_file_info, m_filename_inzip,
213 sizeof(m_filename_inzip), NULL, 0, NULL, 0);
215 return LogAndCreateResult(ErrorCode::UNKNOWN_ERR,
216 getArchiveLogMessage(err, "unzGetCurrentFileInfo()"),
219 LoggerD("Input from ZIP: m_filename_inzip: [%s]", m_filename_inzip);
220 LoggerD("m_base_strip_path: [%s]", m_base_strip_path.c_str());
222 std::string file_path = m_filename_inzip;
223 if (!m_base_strip_path.empty()) {
224 if (file_path.find(m_base_strip_path) != 0) {
225 LoggerW("m_base_strip_path: [%s] is not begin of m_filename_inzip: [%s]!",
226 m_base_strip_path.c_str(), m_filename_inzip);
228 file_path = file_path.substr(m_base_strip_path.length());
229 LoggerD("Stripped file name: [%s]", file_path.c_str());
232 LoggerD("Not stripped file name: [%s]", file_path.c_str());
235 m_output_filepath = removeDuplicatedSlashesFromPath(m_extract_path + "/" + file_path);
237 LoggerD("Packed: [%s], uncompressed_size: %lu, will extract to: [%s]", m_filename_inzip,
238 m_file_info.uncompressed_size, m_output_filepath.c_str());
240 std::string path, name;
241 divideToPathAndName(file_path, path, name);
242 m_new_dir_path = removeDuplicatedSlashesFromPath(m_extract_path + "/" + path);
243 m_new_dir_status = getPathStatus(m_new_dir_path);
244 m_is_directory_entry = name.empty();
246 LoggerD("New output dir: [%s] status: %d m_is_directory_entry: %d", m_new_dir_path.c_str(),
247 m_new_dir_status, m_is_directory_entry);
249 if (FPS_DIRECTORY != m_new_dir_status) {
250 if (m_is_directory_entry) {
251 std::string base_directories;
252 const size_t len = m_new_dir_path.length();
254 for (int i = static_cast<int>(len) - 2; i >= 0; i--) { // skip last \, /
255 const char& cur = m_new_dir_path[i];
256 if ('\\' == cur || '/' == cur) {
257 base_directories = m_new_dir_path.substr(0, static_cast<size_t>(i));
262 LoggerD("Type: DIRECTORY checking base output directories: [%s]", base_directories.c_str());
263 createMissingDirectories(base_directories, false);
265 LoggerD("Type: FILE checking output dir: [%s]", m_new_dir_path.c_str());
266 createMissingDirectories(m_new_dir_path, false);
269 return PlatformResult(ErrorCode::NO_ERROR);
272 PlatformResult UnZipExtractRequest::handleDirectoryEntry() {
274 if (FPS_DIRECTORY != m_new_dir_status) {
275 if (FPS_FILE == m_new_dir_status) {
276 if (m_callback->getOverwrite()) { // Is a file & overwrite is set:
277 std::string fn = removeTrailingDirectorySlashFromPath(m_new_dir_path);
278 if (std::remove(fn.c_str()) != 0) {
279 SLoggerE("std::remove(\"%s\") failed with errno: %s", m_new_dir_path.c_str(),
280 GetErrorString(errno).c_str());
281 return LogAndCreateResult(ErrorCode::UNKNOWN_ERR,
282 "Could not overwrite file in output directory");
284 } else { // Is a file & overwrite is not set:
285 return LogAndCreateResult(ErrorCode::UNKNOWN_ERR,
286 "Failed to extract directory, "
287 "file with the same name exists in output directory");
291 // Try to create new directory in output directory
292 if (mkdir(m_new_dir_path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == -1) {
293 SLoggerE("Couldn't create new directory: %s errno: %s", m_new_dir_path.c_str(),
294 GetErrorString(errno).c_str());
295 return LogAndCreateResult(ErrorCode::UNKNOWN_ERR,
296 "Could not create new directory in extract output directory");
300 LoggerD("Set dir: [%s] access and modify to: %4u-%2u-%2u %2u:%2u:%2u", m_new_dir_path.c_str(),
301 m_file_info.tmu_date.tm_year, m_file_info.tmu_date.tm_mon, m_file_info.tmu_date.tm_mday,
302 m_file_info.tmu_date.tm_hour, m_file_info.tmu_date.tm_min, m_file_info.tmu_date.tm_sec);
304 // change modify date and store permission for later update
306 // Directory already exists we only need to update time
307 changeFileAccessAndModifyDate(m_new_dir_path, m_file_info.tmu_date);
309 LoggerD("Extracted directory entry: [%s]", m_new_dir_path.c_str());
311 return PlatformResult(ErrorCode::NO_ERROR);
314 PlatformResult UnZipExtractRequest::prepareOutputSubdirectory() {
316 // This zip entry points to file - verify that parent directory in output dir exists
317 if (FPS_DIRECTORY != m_new_dir_status) {
318 if (FPS_FILE == m_new_dir_status) {
319 SLoggerE("Path: %s is pointing to file not directory!", m_new_dir_path.c_str());
320 return LogAndCreateResult(ErrorCode::UNKNOWN_ERR,
321 "Failed to extract file from zip archive, "
322 "output path is invalid");
325 // permissions would be changed after extract process would be finished,
326 // for now using default 0775
327 if (mkdir(m_new_dir_path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == -1) {
328 LoggerW("couldn't create new directory: %s errno: %s", m_new_dir_path.c_str(),
329 GetErrorString(errno).c_str());
333 if (m_callback->isCanceled()) {
334 return LogAndCreateResult(ErrorCode::OPERATION_CANCELED_ERR, "Operation canceled");
337 const FilePathStatus output_fstatus = getPathStatus(m_output_filepath);
338 if (FPS_NOT_EXIST != output_fstatus) {
339 if (!m_callback->getOverwrite()) {
340 SLoggerE("%s exists at output path: [%s], overwrite is set to FALSE",
341 (FPS_DIRECTORY == output_fstatus ? "Directory" : "File"), m_output_filepath.c_str());
342 return LogAndCreateResult(ErrorCode::INVALID_MODIFICATION_ERR, "File already exists.");
344 if (FPS_DIRECTORY == output_fstatus) {
345 filesystem::PathPtr path = filesystem::Path::create(m_output_filepath);
346 filesystem::NodePtr node;
347 PlatformResult result = filesystem::Node::resolve(path, &node);
348 if (result.error_code() != ErrorCode::NO_ERROR) {
349 LoggerE("Error: %s", result.message().c_str());
352 result = node->remove(filesystem::OPT_RECURSIVE);
353 if (result.error_code() != ErrorCode::NO_ERROR) {
354 LoggerE("Error: %s", result.message().c_str());
361 return PlatformResult(ErrorCode::NO_ERROR);
364 PlatformResult UnZipExtractRequest::handleFileEntry() {
367 PlatformResult result = prepareOutputSubdirectory();
368 if (result.error_code() != ErrorCode::NO_ERROR) {
369 LoggerE("File exists but overwrite is false");
374 unzOpenCurrentFilePassword(m_owner.m_unzip,
375 NULL); // password is not supported yet therefore passing NULL
377 return LogAndCreateResult(ErrorCode::UNKNOWN_ERR,
378 getArchiveLogMessage(err, "unzOpenCurrentFilePassword()"),
382 // We have successfully opened curent file, therefore we should close it later
383 m_close_unz_current_file = true;
385 const size_t buffer_size = m_owner.m_default_buffer_size;
386 m_buffer = new (std::nothrow) char[buffer_size];
388 return LogAndCreateResult(
389 ErrorCode::UNKNOWN_ERR, "Memory allocation failed",
390 ("Couldn't allocate buffer with size: %s", bytesToReadableString(buffer_size).c_str()));
393 m_output_file = fopen(m_output_filepath.c_str(), "wb");
394 if (!m_output_file) {
395 SLoggerE("Couldn't open output file: %s", m_output_filepath.c_str());
396 return LogAndCreateResult(ErrorCode::UNKNOWN_ERR, "Could not create extracted file");
398 m_delete_output_file = true;
400 bool marked_as_finished = false;
402 LoggerD("Started extracting: [%s] uncompressed size: %lu - %s", m_filename_inzip,
403 m_file_info.uncompressed_size,
404 bytesToReadableString(m_file_info.uncompressed_size).c_str());
406 ExtractAllProgressCallback* extract_callback = NULL;
407 if (m_callback->getCallbackType() == EXTRACT_ALL_PROGRESS_CALLBACK ||
408 m_callback->getCallbackType() == EXTRACT_ENTRY_PROGRESS_CALLBACK) {
409 extract_callback = dynamic_cast<ExtractAllProgressCallback*>(m_callback);
410 if (NULL == extract_callback) {
411 SLoggerE("extract_callback is null");
412 return LogAndCreateResult(ErrorCode::UNKNOWN_ERR, "Could not create extracted file");
414 extract_callback->startedExtractingFile(m_file_info.uncompressed_size);
417 ArchiveFileEntryPtrMapPtr entries = m_callback->getArchiveFile()->getEntryMap();
418 auto it = entries->find(m_filename_inzip);
419 if (it == entries->end()) {
420 return LogAndCreateResult(ErrorCode::NOT_FOUND_ERR, "Entry not found");
424 if (m_callback->isCanceled()) {
425 return LogAndCreateResult(ErrorCode::OPERATION_CANCELED_ERR, "Operation canceled");
428 int read_size = unzReadCurrentFile(m_owner.m_unzip, m_buffer, buffer_size);
430 SLoggerE("unzReadCurrentFile failed with error code: %d for file: %s", read_size,
432 return LogAndCreateResult(ErrorCode::UNKNOWN_ERR, "Failed to extract file from zip archive");
433 } else if (0 == read_size) {
434 if (extract_callback) {
435 if (!marked_as_finished) {
436 LoggerD("NOT marked_as_finished -> increment extracted files counter");
437 extract_callback->finishedExtractingFile();
439 // Call progress callback only if we expected empty file
440 if (m_file_info.uncompressed_size == 0) {
441 LoggerD("Calling progress callback(%f, %s)", extract_callback->getOverallProgress(),
443 extract_callback->callProgressCallbackOnMainThread(
444 extract_callback->getOverallProgress(), it->second);
448 // Finished writing file, we should not delete extracted file
449 m_delete_output_file = false;
453 if (fwrite(m_buffer, read_size, 1, m_output_file) != 1) {
454 return LogAndCreateResult(ErrorCode::UNKNOWN_ERR,
455 "Could not write extract file into output file");
458 if (extract_callback) {
459 extract_callback->extractedPartOfFile(read_size);
460 LoggerD("File: [%s] extracted: %s - %f%%; total progress %f%%", m_filename_inzip,
461 bytesToReadableString(read_size).c_str(),
462 100.0f * extract_callback->getCurrentFileProgress(),
463 100.0f * extract_callback->getOverallProgress());
465 // It is better to update number of extracted entries so we will have
466 // overal progres: 1.0 if all files are extracted
468 if (extract_callback->getCurrentFileProgress() >= 1.0) {
470 "Current file: [%s] progress: %f >= 1.0 -> "
471 "marked_as_finished = true and increment extracted files counter",
472 m_filename_inzip, extract_callback->getCurrentFileProgress());
473 marked_as_finished = true;
474 extract_callback->finishedExtractingFile();
477 LoggerD("Calling progress callback(%f, %s)", extract_callback->getOverallProgress(),
479 extract_callback->callProgressCallbackOnMainThread(extract_callback->getOverallProgress(),
485 fclose(m_output_file);
486 m_output_file = NULL;
489 // change modify date and store permission for later update
491 changeFileAccessAndModifyDate(m_output_filepath, m_file_info.tmu_date);
493 return PlatformResult(ErrorCode::NO_ERROR);
496 void UnZipExtractRequest::storePermissions() {
497 // hold access information for later set
498 // The high 16 bits of the external file attributes seem to be used for
499 // OS-specific permissions https://unix.stackexchange.com/a/14727 this answer describes that
501 // * 4 bits - file type
502 // * 3 bits - setuid, setgid, sticky
503 // * 9 bits - permissions
504 __mode_t mode = m_file_info.external_fa >> 16;
505 unsigned int linux_file_type = mode >> 12;
506 __mode_t default_mode = (S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
507 // check if proper permission mode is provided, if not use default 0775
508 // validation is made for checking if both permissions and file type information is not zero
509 if (mode == 0 || linux_file_type == 0) {
512 "File %s has incomplete information for unix filesystem, using "
513 "default file permissions: %o",
514 m_filename_inzip, mode);
516 LoggerD("Storing permissions for %s: %o", m_filename_inzip, mode);
517 m_owner.path_access_map[m_output_filepath.c_str()] = mode;
520 } // namespace archive
521 } // namespace extension