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()"),
220 LoggerD("Input from ZIP: m_filename_inzip: [%s]", m_filename_inzip);
221 LoggerD("m_base_strip_path: [%s]", m_base_strip_path.c_str());
223 std::string file_path = m_filename_inzip;
224 if (!m_base_strip_path.empty()) {
225 if (file_path.find(m_base_strip_path) != 0) {
226 LoggerW("m_base_strip_path: [%s] is not begin of m_filename_inzip: [%s]!",
227 m_base_strip_path.c_str(), m_filename_inzip);
229 file_path = file_path.substr(m_base_strip_path.length());
230 LoggerD("Stripped file name: [%s]", file_path.c_str());
233 LoggerD("Not stripped file name: [%s]", file_path.c_str());
236 m_output_filepath = removeDuplicatedSlashesFromPath(m_extract_path + "/" + file_path);
238 LoggerD("Packed: [%s], uncompressed_size: %lu, will extract to: [%s]", m_filename_inzip,
239 m_file_info.uncompressed_size, m_output_filepath.c_str());
241 std::string path, name;
242 divideToPathAndName(file_path, path, name);
243 m_new_dir_path = removeDuplicatedSlashesFromPath(m_extract_path + "/" + path);
244 m_new_dir_status = getPathStatus(m_new_dir_path);
245 m_is_directory_entry = name.empty();
247 LoggerD("New output dir: [%s] status: %d m_is_directory_entry: %d", m_new_dir_path.c_str(),
248 m_new_dir_status, m_is_directory_entry);
250 if (FPS_DIRECTORY != m_new_dir_status) {
251 if (m_is_directory_entry) {
252 std::string base_directories;
253 const size_t len = m_new_dir_path.length();
255 for (int i = static_cast<int>(len) - 2; i >= 0; i--) { // skip last \, /
256 const char& cur = m_new_dir_path[i];
257 if ('\\' == cur || '/' == cur) {
258 base_directories = m_new_dir_path.substr(0, static_cast<size_t>(i));
263 LoggerD("Type: DIRECTORY checking base output directories: [%s]", base_directories.c_str());
264 createMissingDirectories(base_directories, false);
266 LoggerD("Type: FILE checking output dir: [%s]", m_new_dir_path.c_str());
267 createMissingDirectories(m_new_dir_path, false);
270 return PlatformResult(ErrorCode::NO_ERROR);
273 PlatformResult UnZipExtractRequest::handleDirectoryEntry() {
275 if (FPS_DIRECTORY != m_new_dir_status) {
276 if (FPS_FILE == m_new_dir_status) {
277 if (m_callback->getOverwrite()) { // Is a file & overwrite is set:
278 std::string fn = removeTrailingDirectorySlashFromPath(m_new_dir_path);
279 if (std::remove(fn.c_str()) != 0) {
280 SLoggerE("std::remove(\"%s\") failed with errno: %s", m_new_dir_path.c_str(),
281 GetErrorString(errno).c_str());
282 return LogAndCreateResult(ErrorCode::UNKNOWN_ERR,
283 "Could not overwrite file in output directory");
285 } else { // Is a file & overwrite is not set:
286 return LogAndCreateResult(ErrorCode::UNKNOWN_ERR,
287 "Failed to extract directory, "
288 "file with the same name exists in output directory");
292 // Try to create new directory in output directory
293 if (mkdir(m_new_dir_path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == -1) {
294 SLoggerE("Couldn't create new directory: %s errno: %s", m_new_dir_path.c_str(),
295 GetErrorString(errno).c_str());
296 return LogAndCreateResult(ErrorCode::UNKNOWN_ERR,
297 "Could not create new directory in extract output directory");
301 LoggerD("Set dir: [%s] access and modify to: %4u-%2u-%2u %2u:%2u:%2u", m_new_dir_path.c_str(),
302 m_file_info.tmu_date.tm_year, m_file_info.tmu_date.tm_mon, m_file_info.tmu_date.tm_mday,
303 m_file_info.tmu_date.tm_hour, m_file_info.tmu_date.tm_min, m_file_info.tmu_date.tm_sec);
305 // change modify date and store permission for later update
307 // Directory already exists we only need to update time
308 changeFileAccessAndModifyDate(m_new_dir_path, m_file_info.tmu_date);
310 LoggerD("Extracted directory entry: [%s]", m_new_dir_path.c_str());
312 return PlatformResult(ErrorCode::NO_ERROR);
315 PlatformResult UnZipExtractRequest::prepareOutputSubdirectory() {
317 // This zip entry points to file - verify that parent directory in output dir exists
318 if (FPS_DIRECTORY != m_new_dir_status) {
319 if (FPS_FILE == m_new_dir_status) {
320 SLoggerE("Path: %s is pointing to file not directory!", m_new_dir_path.c_str());
321 return LogAndCreateResult(ErrorCode::UNKNOWN_ERR,
322 "Failed to extract file from zip archive, "
323 "output path is invalid");
326 // permissions would be changed after extract process would be finished,
327 // for now using default 0775
328 if (mkdir(m_new_dir_path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == -1) {
329 LoggerW("couldn't create new directory: %s errno: %s", m_new_dir_path.c_str(),
330 GetErrorString(errno).c_str());
334 if (m_callback->isCanceled()) {
335 return LogAndCreateResult(ErrorCode::OPERATION_CANCELED_ERR, "Operation canceled");
338 const FilePathStatus output_fstatus = getPathStatus(m_output_filepath);
339 if (FPS_NOT_EXIST != output_fstatus) {
340 if (!m_callback->getOverwrite()) {
341 SLoggerE("%s exists at output path: [%s], overwrite is set to FALSE",
342 (FPS_DIRECTORY == output_fstatus ? "Directory" : "File"), m_output_filepath.c_str());
343 return LogAndCreateResult(ErrorCode::INVALID_MODIFICATION_ERR, "File already exists.");
345 if (FPS_DIRECTORY == output_fstatus) {
346 filesystem::PathPtr path = filesystem::Path::create(m_output_filepath);
347 filesystem::NodePtr node;
348 PlatformResult result = filesystem::Node::resolve(path, &node);
349 if (result.error_code() != ErrorCode::NO_ERROR) {
350 LoggerE("Error: %s", result.message().c_str());
353 result = node->remove(filesystem::OPT_RECURSIVE);
354 if (result.error_code() != ErrorCode::NO_ERROR) {
355 LoggerE("Error: %s", result.message().c_str());
362 return PlatformResult(ErrorCode::NO_ERROR);
365 PlatformResult UnZipExtractRequest::handleFileEntry() {
368 PlatformResult result = prepareOutputSubdirectory();
369 if (result.error_code() != ErrorCode::NO_ERROR) {
370 LoggerE("File exists but overwrite is false");
375 unzOpenCurrentFilePassword(m_owner.m_unzip,
376 NULL); // password is not supported yet therefore passing NULL
378 return LogAndCreateResult(ErrorCode::UNKNOWN_ERR,
379 getArchiveLogMessage(err, "unzOpenCurrentFilePassword()"),
383 // We have successfully opened curent file, therefore we should close it later
384 m_close_unz_current_file = true;
386 const size_t buffer_size = m_owner.m_default_buffer_size;
387 m_buffer = new (std::nothrow) char[buffer_size];
389 return LogAndCreateResult(
390 ErrorCode::UNKNOWN_ERR, "Memory allocation failed",
391 ("Couldn't allocate buffer with size: %s", bytesToReadableString(buffer_size).c_str()));
394 m_output_file = fopen(m_output_filepath.c_str(), "wb");
395 if (!m_output_file) {
396 SLoggerE("Couldn't open output file: %s", m_output_filepath.c_str());
397 return LogAndCreateResult(ErrorCode::UNKNOWN_ERR, "Could not create extracted file");
399 m_delete_output_file = true;
401 bool marked_as_finished = false;
403 LoggerD("Started extracting: [%s] uncompressed size: %lu - %s", m_filename_inzip,
404 m_file_info.uncompressed_size,
405 bytesToReadableString(m_file_info.uncompressed_size).c_str());
407 ExtractAllProgressCallback* extract_callback = NULL;
408 if (m_callback->getCallbackType() == EXTRACT_ALL_PROGRESS_CALLBACK ||
409 m_callback->getCallbackType() == EXTRACT_ENTRY_PROGRESS_CALLBACK) {
410 extract_callback = dynamic_cast<ExtractAllProgressCallback*>(m_callback);
411 if (NULL == extract_callback) {
412 SLoggerE("extract_callback is null");
413 return LogAndCreateResult(ErrorCode::UNKNOWN_ERR, "Could not create extracted file");
415 extract_callback->startedExtractingFile(m_file_info.uncompressed_size);
418 ArchiveFileEntryPtrMapPtr entries = m_callback->getArchiveFile()->getEntryMap();
419 auto it = entries->find(m_filename_inzip);
420 if (it == entries->end()) {
421 return LogAndCreateResult(ErrorCode::NOT_FOUND_ERR, "Entry not found");
425 if (m_callback->isCanceled()) {
426 return LogAndCreateResult(ErrorCode::OPERATION_CANCELED_ERR, "Operation canceled");
429 int read_size = unzReadCurrentFile(m_owner.m_unzip, m_buffer, buffer_size);
431 SLoggerE("unzReadCurrentFile failed with error code: %d for file: %s", read_size,
433 return LogAndCreateResult(ErrorCode::UNKNOWN_ERR, "Failed to extract file from zip archive");
434 } else if (0 == read_size) {
435 if (extract_callback) {
436 if (!marked_as_finished) {
437 LoggerD("NOT marked_as_finished -> increment extracted files counter");
438 extract_callback->finishedExtractingFile();
440 // Call progress callback only if we expected empty file
441 if (m_file_info.uncompressed_size == 0) {
442 LoggerD("Calling progress callback(%f, %s)", extract_callback->getOverallProgress(),
444 extract_callback->callProgressCallbackOnMainThread(
445 extract_callback->getOverallProgress(), it->second);
449 // Finished writing file, we should not delete extracted file
450 m_delete_output_file = false;
454 if (fwrite(m_buffer, read_size, 1, m_output_file) != 1) {
455 return LogAndCreateResult(ErrorCode::UNKNOWN_ERR,
456 "Could not write extract file into output file");
459 if (extract_callback) {
460 extract_callback->extractedPartOfFile(read_size);
461 LoggerD("File: [%s] extracted: %s - %f%%; total progress %f%%", m_filename_inzip,
462 bytesToReadableString(read_size).c_str(),
463 100.0f * extract_callback->getCurrentFileProgress(),
464 100.0f * extract_callback->getOverallProgress());
466 // It is better to update number of extracted entries so we will have
467 // overal progres: 1.0 if all files are extracted
469 if (extract_callback->getCurrentFileProgress() >= 1.0) {
471 "Current file: [%s] progress: %f >= 1.0 -> "
472 "marked_as_finished = true and increment extracted files counter",
473 m_filename_inzip, extract_callback->getCurrentFileProgress());
474 marked_as_finished = true;
475 extract_callback->finishedExtractingFile();
478 LoggerD("Calling progress callback(%f, %s)", extract_callback->getOverallProgress(),
480 extract_callback->callProgressCallbackOnMainThread(extract_callback->getOverallProgress(),
486 fclose(m_output_file);
487 m_output_file = NULL;
490 // change modify date and store permission for later update
492 changeFileAccessAndModifyDate(m_output_filepath, m_file_info.tmu_date);
494 return PlatformResult(ErrorCode::NO_ERROR);
497 void UnZipExtractRequest::storePermissions() {
498 // hold access information for later set
499 // The high 16 bits of the external file attributes seem to be used for OS-specific permissions
500 __mode_t mode = m_file_info.external_fa >> 16;
501 // check if proper permission mode is provided, if not use default 0775
503 mode = S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH;
505 m_owner.path_access_map[m_output_filepath.c_str()] = mode;
508 } // namespace archive
509 } // namespace extension